﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections;
using System.Reflection;
using System.Xml;

namespace JWFW.SFL
{
    public class AMFOP
    {

    }

    public class AMFMessage1
    {
        public enum DataType
        {
            Number = 0,
            Boolean = 1,
            String = 2,
            UntypedObject = 3,
            MovieClip = 4,
            Null = 5,
            Undefined = 6,
            ReferencedObject = 7,
            MixedArray = 8,
            End = 9,
            Array = 10,
            Date = 11,
            LongString = 12,
            TypeAsObject = 13,
            Recordset = 14,
            Xml = 15,
            TypedObject = 16,
            AMF3Tag = 17
        }
        public enum AMF3DataType
        {
            Undefined = 0,
            Null = 1,
           BooleanFalse = 2,
           BooleanTrue = 3,
           Integer = 4,
           Number = 5,
           String = 6,
           Xml2 = 7,
           DateTime = 8,
           Array = 9,
           Object = 10,
           Xml = 11,
           ByteArray = 12
        }
        public class Header
        {
            #region Fields and Properties
            private string m_key;
            private bool m_required;
            private object m_data;

            public string Key
            {
                get { return m_key; }
                set { m_key = value; }
            }
            public bool Required
            {
                get { return m_required; }
                set { m_required = value; }
            }
            public object Data
            {
                get { return m_data; }
                set { m_data = value; }
            }
            #endregion

            #region Constructors
            public Header(string key, bool required, object data)
            {
                m_key = key;
                m_required = required;
                m_data = data;
            }
            #endregion
        }
        public class Body
        {
            #region Fields and Properties
            private readonly static Type m_int32Type = Type.GetType("System.Int32");
            private readonly static Type m_int64Type = Type.GetType("System.Int64");
            private string m_response = "";
            private string m_target = "";
            private object m_content = null;

            public string Target
            {
                get { return m_target; }
                set { m_target = value; }
            }
            public string Response
            {
                get { return m_response; }
                set { m_response = value; }
            }
            public object Content
            {
                get { return m_content; }
                set { m_content = value; }
            }

            #endregion

            #region Constructors

            public Body()
            {
            }
            public Body(string response, string target, object data)
            {
                m_response = response;
                m_target = target;
                m_content = data;
            }

            #endregion
        }

        public class MessageBase
        {
        }
        public class RemotingMessage : MessageBase
        {
            public string Name = "flex.messaging.messages.RemotingMessage";
            public string operation = "";
            public string source = "";
            public string clientId = "";
            public object body = null;
            public int timeToLive = 0;
            public string messageId = "";
            public string destination = "";
            public int timestamp = 0;
            public object headers = new Dictionary<string,string>();

            public List<string> propertiesOrder = new List<string>()
            {
                "operation","source","clientId","body","timeToLive","messageId","destination","timestamp","headers"
            };
        }

        public AMFMessage1()
        {
            ms = new MemoryStream(bytes);
            bw = new BinaryWriter(ms);
        }

        private byte[] bytes = new byte[2048];
        private MemoryStream ms = null;
        private BinaryWriter bw = null;
        private DateTime baseDate = new DateTime(1970, 1, 1);

        public ushort Version = 3;
        private List<Header> Headers = new List<Header>();
        private List<Body> Bodies = new List<Body>();

        public byte[] ToHexBytes()
        {
            AppendAll();
            return HexOP.TruncateBytes(bytes, (int)ms.Position);
        }

        protected void AppendAll()
        {
            AppendPreamble();
            AppendHeader();
            AppendBody();
        }

        protected void AppendPreamble()
        {
            WriteUInt16(Version);
        }
        protected void AppendHeader()
        {
            WriteUInt16((UInt16)Headers.Count);
            foreach (Header header in Headers)
            {
                WriteString(header.Key);
                bw.Write(header.Required);
                bw.Write(-1);
                WriteData(header.Data);
            }
        }
        protected void AppendBody()
        {
            WriteUInt16((UInt16)Bodies.Count);
            foreach (Body b in Bodies)
            {
                WriteString(b.Response == null ? "null" : b.Response);
                WriteString(b.Target == null ? "null" : b.Target);
                bw.Write(-1);
                WriteData(b.Content);
            }
        }

        #region Private Methods

        private void WriteData(object data)
        {
            if (data == null || data == DBNull.Value)
            {
               bw.Write((byte)DataType.Null);
            }
            else if (data is string)
            {
                string str = (string)data;
                if (str.Length < UInt16.MaxValue)
                {
                   bw.Write((byte)DataType.String);
                    WriteString(str);
                }
                else
                {
                   bw.Write((byte)DataType.LongString);
                    WriteLongString(str);
                }
            }
            else if (data is char)
            {
               bw.Write((byte)DataType.String);
                WriteString(((char)data).ToString());
            }
            else if (data is bool)
            {
               bw.Write((byte)DataType.Boolean);
               bw.Write((bool)data);
            }
            else if (data is DateTime)
            {
               bw.Write((byte)DataType.Date);
                WriteDate((DateTime)data);

            }
            else if (IsNumber(data))
            {
               bw.Write((byte)DataType.Number);
                double d = Convert.ToDouble(data.ToString());
                WriteDouble(d);
            }
            else if (data is XmlDocument)
            {
               bw.Write((byte)DataType.Xml);
                WriteLongString(((XmlDocument)data).OuterXml);
            }
            else if (data is IDictionary)
            {
               bw.Write((byte)DataType.MixedArray);
                WriteDictionary((IDictionary)data);
            }
            else if (data is ICollection)
            {
               bw.Write((byte)DataType.Array);
                WriteCollection((ICollection)data);
            }
            else if (data.GetType().IsArray)
            {
                ArrayList arr = new ArrayList((object[])data);
               bw.Write((byte)DataType.Array);
                WriteCollection(arr);
            }
            else if (data is MessageBase)
            {

            }
            else
            {
                bw.Write((byte)DataType.TypedObject);
                WriteCustomClass(data);
            }
        }
        private void WriteCustomClass(object value)
        {
            Type type = value.GetType();
            WriteString(type.FullName);
            PropertyInfo[] properties = type.GetProperties();
            foreach (PropertyInfo property in properties)
            {
                WriteString(property.Name);
                WriteData(property.GetValue(value, null));
            }
            WriteEnd();
        }
        private void WriteEnd()
        {
            WriteUInt16(0);
            bw.Write((byte)DataType.End);
        }
        private void WriteDictionary(IDictionary value)
        {
            WriteUInt32((UInt32)value.Count);
            IDictionaryEnumerator enumerator = value.GetEnumerator();
            while (enumerator.MoveNext())
            {
                //todo throw exception if key isn't string
                WriteString(enumerator.Key.ToString());
                WriteData(enumerator.Value);
            }
        }
        private void WriteCollection(ICollection value)
        {
            WriteUInt32((UInt32)value.Count);
            foreach (object o in value)
            {
                WriteData(o);
            }
        }
        private void WriteDate(DateTime date)
        {
            TimeSpan elapsed = date.Subtract(baseDate);
            long l = (long)elapsed.TotalMilliseconds;
            WriteInternal(BitConverter.GetBytes(BitConverter.DoubleToInt64Bits((double)l)));
            WriteUInt16((UInt16)65236);
        }

        private void WriteDouble(double value)
        {
            WriteInternal(BitConverter.GetBytes(value));
        }
        private void WriteUInt32(UInt32 value)
        {
            WriteInternal(BitConverter.GetBytes(value));
        }
        private void WriteUInt16(UInt16 value)
        {
            WriteInternal(BitConverter.GetBytes(value));
        }
        private void WriteString(string value)
        {
            int length = value.Length;
            WriteUInt16((UInt16)length);
            byte[] buffer = Encoding.UTF8.GetBytes(value);
            bw.Write(buffer, 0, length);
        }
        private void WriteLongString(string value)
        {
            int length = value.Length;
            WriteUInt32((UInt32)length);
            byte[] buffer = Encoding.UTF8.GetBytes(value);
            bw.Write(buffer, 0, length);
        }

        private void WriteInternal(byte[] value)
        {
            for (int i = value.Length - 1; i >= 0; --i)
            {
                bw.Write(value[i]);
            }
        }
        private bool IsNumber(object data)
        {
            return (data is int || data is long || data is short || data is float || data is byte || data is sbyte || data is uint || data is ulong || data is ushort || data is double);
        }

        #endregion

        private void WriteAMF3Data(object data)
        {
            if (data == null || data == DBNull.Value)
            {
                bw.Write((byte)DataType.Null);
            }
            else if (data is string)
            {
                string str = (string)data;
                if (str.Length < UInt16.MaxValue)
                {
                    bw.Write((byte)DataType.String);
                    WriteString(str);
                }
                else
                {
                    bw.Write((byte)DataType.LongString);
                    WriteLongString(str);
                }
            }
            else if (data is char)
            {
                bw.Write((byte)DataType.String);
                WriteString(((char)data).ToString());
            }
            else if (data is bool)
            {
                bw.Write((byte)DataType.Boolean);
                bw.Write((bool)data);
            }
            else if (data is DateTime)
            {
                bw.Write((byte)DataType.Date);
                WriteDate((DateTime)data);

            }
            else if (IsNumber(data))
            {
                bw.Write((byte)DataType.Number);
                double d = Convert.ToDouble(data.ToString());
                WriteDouble(d);
            }
            else if (data is XmlDocument)
            {
                bw.Write((byte)DataType.Xml);
                WriteLongString(((XmlDocument)data).OuterXml);
            }
            else if (data is IDictionary)
            {
                bw.Write((byte)DataType.MixedArray);
                WriteDictionary((IDictionary)data);
            }
            else if (data is ICollection)
            {
                bw.Write((byte)DataType.Array);
                WriteCollection((ICollection)data);
            }
            else if (data.GetType().IsArray)
            {
                ArrayList arr = new ArrayList((object[])data);
                bw.Write((byte)DataType.Array);
                WriteCollection(arr);
            }
            else
            {
                bw.Write((byte)DataType.TypedObject);
                WriteCustomClass(data);
            }
        }



        private void Test()
        {
            AMFMessage1 msg = new AMFMessage1();
            Body b = new Body();
            msg.Bodies.Add(b);
            b.Response = "null";
            b.Target = @"/3";
            RemotingMessage rm = new RemotingMessage();
            b.Content = new object[] { rm };
            string res = HexOP.BytesToHex(msg.ToHexBytes());
            Console.WriteLine(res);
        }
    }
}
